"""函数的参数"""
# 除了正常定义的必选参数外，还可以使用默认参数、可变参数和关键字参数，使得函数定义出来的接口，不但能处理复杂的参数，还可以简化调用者的代码。

def power(x):
    return x * x
print(power(3))

def power(x, n):
    sum = 1
    while n > 0:
        sum = sum * x
        n = n - 1
    return sum
print(power(3, 3))

# 默认参数
# 新的power(x, n)函数定义没有问题，但是，旧的调用代码失败了，原因是我们增加了一个参数，导致旧的代码因为缺少一个参数而无法正常调用：
def power(x, n = 2):
    sum = 1
    while n > 0:
        sum = sum * x
        n = n - 1
    return sum
print(power(3))
# 设置默认参数时，有几点要注意：
# 一是必选参数在前，默认参数在后，否则Python的解释器会报错
# 二是当函数有多个参数时，把变化大的参数放前面，变化小的参数放后面。变化小的参数就可以作为默认参数

def enroll(name, gender):
    print('name', name)
    print('gender', gender)

enroll('zhangsan', 'M')

# 如果要继续传入年龄、城市等信息怎么办？这样会使得调用函数的复杂度大大增加。
# 我们可以把年龄和城市设为默认参数：
def enroll(name, gender, age = 18, city = 'Beijing'):
    print('name', name)
    print('gender', gender)
    print('age', age)
    print('city', city)
enroll('zhangsan', 'M')
enroll('lisi', 'M', 6, 'Wuhan')
enroll('wangwu', 'M', 16)
# 当不按顺序提供部分默认参数时，需要把参数名写上。
enroll('wangermazi', 'F', city = 'Yantai')

# def add_end(L = []):
#     L.append('end')
#     return L
# print(add_end(['a', 'b', 'c', 'd']))
# print(add_end())
# # 但是，再次调用add_end()时，结果就不对了：默认参数是[]，但是函数似乎每次都“记住了”上次添加了'END'后的list。
# print(add_end())
# print(add_end())

# Python函数在定义的时候，默认参数L的值就被计算出来了，即[]，因为默认参数L也是一个变量，它指向对象[]，每次调用该函数，如果改变了L的内容，则下次调用时，默认参数的内容就变了，不再是函数定义时的[]了。
# 所以，定义默认参数要牢记一点：默认参数必须指向不变对象！

# 要修改上面的例子，我们可以用None这个不变对象来实现：
def add_end(L = None):
    if L is None:
        L = []
    L.append('end')
    return L
print(add_end())
print(add_end())

# 把a，b，c……作为一个list或tuple传进来
def calc(nums):
    sum = 0
    for num in nums:
        sum = sum + num
    return sum
print(calc([1, 2, 3]))
print(calc((1, 2, 4, 7)))
# 定义可变参数
def calc(*nums):
    sum = 0
    for num in nums:
        sum = sum + num
    return sum
print(calc(1, 2, 3))
print(calc(1, 2, 4, 7))

# Python允许你在list或tuple前面加一个*号，把list或tuple的元素变成可变参数传进去
nums = [1, 2, 3, 4, 5]
print(calc(*nums))

# 关键字参数允许你传入0个或任意个含参数名的参数，这些关键字参数在函数内部自动组装为一个dict。
def person(name, age, **kw):
    print('name:', name, 'age:', age, 'other:', kw)
person('zhangsan', 16)
person('zhangsan', 18, city = 'Beijing')
person('zhangsan', 18, city = 'Beijing', gender = 'M')
# 关键字参数有什么用？它可以扩展函数的功能。比如，在person函数里，我们保证能接收到name和age这两个参数，但是，如果调用者愿意提供更多的参数，我们也能收到。
# **extra表示把extra这个dict的所有key-value用关键字参数传入到函数的**kw参数，kw将获得一个dict，
# 注意kw获得的dict是extra的一份拷贝，对kw的改动不会影响到函数外的extra。
extra = {'city': 'Beijing', 'gender': 'M'}
person('zhangsan', 18, **extra)
# 对于关键字参数，函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些，就需要在函数内部通过kw检查。
def person(name, age, **kw):
    if 'city' in kw:
        pass
    if 'gender' in kw:
        pass
    print('name:', name, 'age:', age, 'other:', kw)
person('Jack', 24, city='Beijing', addr='Chaoyang', zipcode=123456)
# 如果要限制关键字参数的名字，就可以用命名关键字参数，例如，只接收city和job作为关键字参数。
# 和关键字参数**kw不同，命名关键字参数需要一个特殊分隔符*，*后面的参数被视为命名关键字参数。
def person(name, age, *, city, job):
    print('name:', name, 'age:', age, 'city:', city, 'job:', job)
person('Jack', 24, city='Beijing', job='Engineer')
# 命名关键字参数必须传入参数名，这和位置参数不同。如果没有传入参数名，调用将报错：
# person('Jack', 24, 'Beijing', 'Engineer')
# 由于命名关键字参数city具有默认值，调用时，可不传入city参数：
def person(name, age, *, city='Beijing', job):
    print(name, age, city, job)
person('Jack', 24, job='Engineer')

# 在Python中定义函数，可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数，这5种参数都可以组合使用，除了可变参数无法和命名关键字参数混合。
# 但是请注意，参数定义的顺序必须是：必选参数、默认参数、可变参数/命名关键字参数和关键字参数。
def f1(a, b, c=0, *args, **kw):
    print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)

def f2(a, b, c=0, *, d, **kw):
    print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)


f1(1, 2)
f1(1, 2, c=3)
f1(1, 2, 3, 'a', 'b')
f1(1, 2, 3, 'a', 'b', x=99)
f2(1, 2, d=99, ext=None)
# 通过一个tuple和dict，你也可以调用上述函数：
# 对于任意函数，都可以通过类似func(*args, **kw)的形式调用它，无论它的参数是如何定义的。
args = (1, 2, 3, 4)
kw = {'d': 99, 'x': '#'}
f1(*args, **kw)
args = (1, 2, 3)
kw = {'d': 88, 'x': '#'}
f2(*args, **kw)


